using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.IO;
using System.Xml.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LitJson;
using System.Text.RegularExpressions;

namespace ConfigGenerator
{
    public enum ESideType
    {
        /// <summary>
        /// 客户端
        /// </summary>
        Client = 0,
        /// <summary>
        /// 服务器
        /// </summary>
        Server = 1,
    }

    /// <summary>
    /// 配置表生成器
    /// </summary>
    public class ConfigGenerator
    {
        /// <summary>
        /// Xlsx文件记录配置
        /// </summary>
        private FileRecordConfig fileRecordConfig;
        /// <summary>
        /// 缓存已生成的Xmlsheet名称
        /// </summary>
        private List<string> sheetXmlNameList = new List<string>();
        /// <summary>
        /// 缓存配置表中客户端的多语言文本，用于后续LangPackageCfg的生成
        /// </summary>
        private Dictionary<string, string> clientLangPackageDict = new Dictionary<string, string>();
        /// <summary>
        /// 缓存配置表中服务器的多语言文本，用于后续LangPackageCfg的生成
        /// </summary>
        private Dictionary<string, string> servertLangPackageDict = new Dictionary<string, string>();
        /// <summary>
        /// 字段范围 s:服务器 c:客户端
        /// </summary>
        private const int RowSideOffset = 0;
        /// <summary>
        /// 字段类型
        /// </summary>
        private const int RowTypeOffset = 1;
        /// <summary>
        /// 字段名称
        /// </summary>
        private const int RowFieldOffset = 2;
        /// <summary>
        /// 字段注释信息
        /// </summary>
        private const int RowCommentOffset = 3;

        /// <summary>
        /// 批量生成生成配置文件
        /// </summary>
        public void GeneratorCfg(EGenType genType, string originExcelDir, string clientXmlConfigDir, string serverXmlConfigDir, string clientLangPackagePath, string serverLangPackagePath)
        {
            ReadJson();

            if (!Directory.Exists(originExcelDir))
            {
                Debug.LogYellow(string.Format("当前未找到Excel配置对应目录，程序自动创建Excel配置对应目录:\n {0} \n", originExcelDir));
                Directory.CreateDirectory(originExcelDir);
            }

            if (!clientXmlConfigDir.Equals("_") && !Directory.Exists(clientXmlConfigDir))
            {
                Debug.LogYellow(string.Format("当前未找到客户端Xml配置目录路径，程序自动创建客户端Xml配置目录：\n {0}", clientXmlConfigDir));
                Directory.CreateDirectory(clientXmlConfigDir);
            }
            if (!serverXmlConfigDir.Equals("_") && !Directory.Exists(serverXmlConfigDir))
            {
                Debug.LogYellow(string.Format("当前未找到服务器Xml配置目录路径，程序自动创建服务器Xml配置目录：\n {0}\n", serverXmlConfigDir));
                Directory.CreateDirectory(serverXmlConfigDir);
            }

            Debug.LogYellow(string.Format("读取Excel文件的根目录：\n{0}\n", originExcelDir));
            List<string> totalFileList = Directory.GetFiles(originExcelDir, "*.xlsx", SearchOption.AllDirectories).ToList();  //获取所有的.xlsx文件路径
            List<string> fileList = new List<string>();  //需要读取的Excel文件列表
            switch (genType)
            {
                case EGenType.All:  //目录下全部文件
                    fileList = totalFileList;
                    break;
                case EGenType.OnlyChange:  //目录下发生修改的文件
                    foreach (var file in totalFileList)
                    {
                        FileInfo fileInfo = new FileInfo(file);
                        if (fileRecordConfig.FileRecordDict.TryGetValue(fileInfo.Name, out FileRecord fileRecord))  //已记录过，则比对更新时间
                        {
                            string recordUpdateDateTimeStr = fileRecord.UpdateTime.ToString();
                            string fileUpdateDateTimeStr = fileInfo.LastWriteTime.ToString();
                            if (!recordUpdateDateTimeStr.Equals(fileUpdateDateTimeStr))
                            {
                                fileList.Add(file);
                            }
                        }
                        else //未记录过，直接放入需要读取的文件列表
                        {
                            fileList.Add(file);
                        }
                    }

                    break;
                case EGenType.Select:  //指定的目标文件
                    Debug.LogYellow("请拖入指定的文件：");
                    string str = Console.ReadLine();
                    fileList.Add(str);
                    break;
                default:
                    fileList = totalFileList;
                    break;
            }

            Debug.LogYellow("开始生成配置表...\n\n");
            if (fileList.Count <= 0)
            {
                Debug.LogError(string.Format("需要生成的文件数量为0!!!"));
            }
            foreach (var file in fileList)
            {
                Debug.LogYellow(string.Format("正在加载文件：{0}", file));
                try
                {
                    FileStream fs = File.OpenRead(file);
                    try
                    {
                        XSSFWorkbook wk = new XSSFWorkbook(fs);
                        BuildXmlConfig(file, wk, clientXmlConfigDir, serverXmlConfigDir);

                        FileRecord fileRecord = null;
                        FileInfo fileInfo = new FileInfo(file);
                        if (!fileRecordConfig.FileRecordDict.TryGetValue(fileInfo.Name, out fileRecord))
                        {
                            fileRecord = new FileRecord();
                            fileRecordConfig.FileRecordDict.Add(fileInfo.Name, fileRecord);
                        }
                        fileRecord.FileName = fileInfo.Name;
                        fileRecord.CreateTime = fileInfo.CreationTime;
                        fileRecord.UpdateTime = fileInfo.LastWriteTime;
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                    }
                }
                catch (Exception)
                {
                    Debug.LogError(string.Format("加载文件：{0}  失败，请检查路径是否有误!!!", file));
                }
            }
            WriteJson();
            DealLangPackage(clientLangPackagePath, ESideType.Client);
            DealLangPackage(serverLangPackagePath, ESideType.Server);
            Debug.LogYellow("\n\n配置表生成结束！");
        }

        /// <summary>
        /// 生成xml配置表文件
        /// </summary>
        /// <param name="filePath">源文件路径</param>
        /// <param name="wk">xlsx文件内容</param>
        /// <param name="clientConfigDir">客户端配置表目录</param>
        /// <param name="serverConfigDir">服务器配置表目录</param>
        public void BuildXmlConfig(string filePath, XSSFWorkbook wk, string clientConfigDir, string serverConfigDir)
        {
            for (int sheetNumber = 0; sheetNumber < wk.NumberOfSheets; sheetNumber++)
            {
                //只会处理名称以“|”分开的sheet作为配置表
                ISheet sheet = wk.GetSheetAt(sheetNumber);
                string[] sheetNames = sheet.SheetName.Split('|', StringSplitOptions.RemoveEmptyEntries);
                if (sheetNames.Length != 2 || string.IsNullOrEmpty(sheetNames[0]) || string.IsNullOrEmpty(sheetNames[1]))
                {
                    continue;
                }

                string elementName = sheetNames[1];  //生成的xml文件的节点名称
                if (sheetXmlNameList.Contains(elementName))
                {
                    Debug.LogError(string.Format("已存在sheet名称：{0}，请检查是否命名重复!!!", elementName));
                    continue;
                }

                string clientFilePath = string.Empty;  //客户端配置表文件名称
                string serverFilePath = string.Empty;  //服务器配置表文件名称
                if (!clientConfigDir.Equals("_"))
                {
                    clientFilePath = string.Format("{0}/{1}Cfg.xml", clientConfigDir, elementName);
                }
                if (!serverConfigDir.Equals("_"))
                {
                    serverFilePath = string.Format("{0}/{1}Cfg.xml", serverConfigDir, elementName);  //服务器配置表文件名称
                }

                IRow rowSide = sheet.GetRow(sheet.FirstRowNum + RowSideOffset);  //字段范围 s:服务器 c:客户端
                IRow rowType = sheet.GetRow(sheet.FirstRowNum + RowTypeOffset);  //字段类型
                IRow rowField = sheet.GetRow(sheet.FirstRowNum + RowFieldOffset);  //字段名称
                IRow rowComment = sheet.GetRow(sheet.FirstRowNum + RowCommentOffset);  //字段注释信息
                int rowNum = sheet.LastRowNum;  //最后一行
                int columnNum = rowSide.LastCellNum;  //最后一列

                bool isGenClient = false;  //是否需要生成客户端配置表
                bool isGenServer = false;  //是否需要生成服务器配置表

                XDocument clientXDoc = new XDocument();  //客户端XDocument
                XElement clientRootEle = new XElement("Config");  //客户端根节点
                clientXDoc.Add(clientRootEle);
                XDocument serverXDoc = new XDocument();  //服务器XDocument
                XElement serverRootEle = new XElement("Config");  //服务器根节点
                serverXDoc.Add(serverRootEle);

                for (int row = sheet.FirstRowNum + RowCommentOffset + 1; row <= rowNum; row++)
                {
                    IRow rowData = sheet.GetRow(row);
                    if (rowData == null)
                        continue;
                    if (rowData.GetCell(0) == null || string.IsNullOrEmpty(rowData.GetCell(0).ToString()))
                        continue;

                    int id = Convert.ToInt32(rowData.GetCell(0).ToString());
                    XElement clientEle = new XElement(elementName);  //客户端节点
                    XElement serverEle = new XElement(elementName);  //服务器节点

                    for (int col = 0; col < columnNum; col++)
                    {
                        string side = rowSide.GetCell(col).StringCellValue;  //字段范围 s:服务器 c:客户端
                        if (!side.Contains('c') && !side.Contains('s'))
                        {
                            continue;
                        }

                        if (side.Contains('c'))
                            isGenClient = true;
                        if (side.Contains('s'))
                            isGenServer = true;

                        string type = rowType.GetCell(col).StringCellValue.ToLower();  //字段类型
                        string field = rowField.GetCell(col).StringCellValue;  //字段名称
                        ICell cell = rowData.GetCell(col);  //该行数据
                        if (cell == null)
                        {
                            if (side.Contains('c'))
                            {
                                if (clientEle.Attribute(field) == null)
                                {
                                    clientEle.SetAttributeValue(field, "");
                                }
                            }
                            if (side.Contains('s'))
                            {
                                if (serverEle.Attribute(field) == null)
                                {
                                    serverEle.SetAttributeValue(field, "");
                                }
                            }
                            continue;
                        }

                        string cellValue = cell.ToString();
                        bool parseSuccess = true;  //解析是否成功
                        //姑且判断一下格式是否正确
                        switch (type)
                        {
                            case "string":
                                parseSuccess = true;
                                break;
                            case "byte":
                                {
                                    byte var;
                                    parseSuccess = byte.TryParse(cellValue, out var);
                                }
                                break;
                            case "short":
                                {
                                    short var;
                                    parseSuccess = short.TryParse(cellValue, out var);
                                }
                                break;
                            case "ushort":
                                {
                                    ushort var;
                                    parseSuccess = ushort.TryParse(cellValue, out var);
                                }
                                break;
                            case "int":
                                {
                                    int var;
                                    parseSuccess = int.TryParse(cellValue, out var);
                                }
                                break;
                            case "uint":
                                {
                                    uint var;
                                    parseSuccess = uint.TryParse(cellValue, out var);
                                }
                                break;
                            case "long":
                                {
                                    long var;
                                    parseSuccess = long.TryParse(cellValue, out var);
                                }
                                break;
                            case "ulong":
                                {
                                    ulong var;
                                    parseSuccess = ulong.TryParse(cellValue, out var);
                                }
                                break;
                            case "float":
                                {
                                    float var;
                                    parseSuccess = float.TryParse(cellValue, out var);
                                }
                                break;
                            case "double":
                                {
                                    double var;
                                    parseSuccess = double.TryParse(cellValue, out var);
                                }
                                break;
                            case "string[]":
                                parseSuccess = true;
                                break;
                            case "byte[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        byte var;
                                        if (!byte.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "short[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        short var;
                                        if (!short.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "ushort[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        ushort var;
                                        if (!ushort.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "int[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        int var;
                                        if (!int.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "uint[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        uint var;
                                        if (!uint.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "long[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        long var;
                                        if (!long.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "ulong[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        ulong var;
                                        if (!ulong.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "float[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        float var;
                                        if (!float.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            case "double[]":
                                {
                                    string[] varArg = cellValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
                                    for (int i = 0; i < varArg.Length; i++)
                                    {
                                        double var;
                                        if (!double.TryParse(varArg[i], out var))
                                        {
                                            parseSuccess = false;
                                        }
                                    }
                                }
                                break;
                            default:
                                {
                                    Debug.LogError(string.Format("解析文件 {0} 出错，sheet名为 {1} 。不支持类型： {2} ！", filePath, sheet.SheetName, type));
                                    return;
                                }
                        }

                        //解析未成功
                        if (!parseSuccess)
                        {
                            Debug.LogError(string.Format("解析文件 {0} 出错，sheet名为 {1} 。第 {2} 行，第 {3} 列，类型为： {4} ，实际参数为： {5} ，不可解析！", filePath, sheet.SheetName, row + 1, col + 1, type, cellValue));

                            return;
                        }

                        if (side.Contains('c'))
                        {
                            SetAttribute(clientEle, field, cellValue, elementName, id, ESideType.Client);
                        }
                        if (side.Contains('s'))
                        {
                            SetAttribute(serverEle, field, cellValue, elementName, id, ESideType.Server);
                        }
                    }

                    clientRootEle.Add(clientEle);
                    serverRootEle.Add(serverEle);
                }

                if (isGenClient || isGenClient)
                {
                    sheetXmlNameList.Add(elementName);
                }

                if (isGenClient && !string.IsNullOrEmpty(clientFilePath))
                {
                    clientXDoc.Save(clientFilePath);
                    Debug.LogDarkGreen(string.Format("生成客户端Xml配置： {0} 成功！    配置表名称：{1}Cfg.xml", sheet.SheetName, elementName));
                }
                if (isGenServer && !string.IsNullOrEmpty(serverFilePath))
                {
                    serverXDoc.Save(serverFilePath);
                    Debug.LogGreen(string.Format("生成服务器Xml配置： {0} 成功！    配置表名称：{1}Cfg.xml", sheet.SheetName, elementName));
                }
            }
        }

        /// <summary>
        /// Xml设置节点属性 同名属性会合并
        /// </summary>
        private void SetAttribute(XElement ele, string attrName, string attrValue, string elementName, int id, ESideType side)
        {
            if (ele.Attribute(attrName) == null)
            {
                if (attrValue.StartsWith("#"))
                {
                    string langPackageKey = string.Format("#{0}_{1}Cfg_{2}_{3}", side, elementName, attrName, id);
                    string langPackageValue = attrValue.Substring(1);
                    if (side == ESideType.Client)
                        clientLangPackageDict.Add(langPackageKey, langPackageValue);
                    else if (side == ESideType.Server)
                        servertLangPackageDict.Add(langPackageKey, langPackageValue);
                    attrValue = langPackageKey;
                }
                ele.SetAttributeValue(attrName, attrValue);
            }
            else
            {
                string tempValue = ele.Attribute(attrName).Value;
                int len = tempValue.Split('|').Length;
                if (attrValue.StartsWith("#"))
                {
                    string langPackageKey = string.Format("#{0}_{1}Cfg_{2}_{3}_{4}", side, elementName, attrName, id, len);
                    string langPackageValue = attrValue.Substring(1);
                    if (side == ESideType.Client)
                        clientLangPackageDict.Add(langPackageKey, langPackageValue);
                    else if (side == ESideType.Server)
                        servertLangPackageDict.Add(langPackageKey, langPackageValue);
                    attrValue = langPackageKey;
                }
                tempValue += "|" + attrValue;
                ele.SetAttributeValue(attrName, tempValue);
            }
        }

        /// <summary>
        /// 读取Json文件
        /// </summary>
        private void ReadJson()
        {
            string jsonPath = "FileRecordConfig.json";
            if (File.Exists(jsonPath))
            {
                StreamReader streamReader = new StreamReader(jsonPath);
                string content = streamReader.ReadToEnd();
                if (string.IsNullOrEmpty(content))
                {
                    fileRecordConfig = new FileRecordConfig();
                }
                else
                {
                    fileRecordConfig = JsonMapper.ToObject<FileRecordConfig>(content);
                }
                streamReader.Close();
            }
            else
            {
                fileRecordConfig = new FileRecordConfig();
            }

        }

        /// <summary>
        /// 写入Json文件
        /// </summary>
        private void WriteJson()
        {
            JsonWriter jw = new JsonWriter();
            jw.PrettyPrint = true;
            JsonMapper.ToJson(fileRecordConfig, jw);
            string jsonStr = jw.ToString();
            Regex reg = new Regex(@"(?i)\\[uU]([0-9a-f]{4})");
            jsonStr = reg.Replace(jsonStr, delegate (Match m) { return ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString(); });
            StreamWriter streamWriter = new StreamWriter("FileRecordConfig.json");
            streamWriter.Write(jsonStr);
            streamWriter.Flush();
            streamWriter.Close();
        }

        #region 配置表多语言处理
        /// <summary>
        /// 处理配置表里的多语言文本
        /// </summary>
        private void DealLangPackage(string langPackagePath, ESideType side)
        {
            if (langPackagePath.Equals("_"))
                return;

            //记录已在xml文件中的语言
            Dictionary<string, LangPackageCfg> docLangPackageDict = new Dictionary<string, LangPackageCfg>();

            if(File.Exists(langPackagePath))
            {
                XDocument xDoc = XDocument.Load(langPackagePath);
                XElement rootEle = xDoc.Root;
                foreach (var ele in rootEle.Elements())
                {
                    string langKey = ele.Attribute("LangKey").Value;
                    string content = ele.Attribute("Content").Value;
                    string content_En = string.Empty;
                    if(ele.Attribute("Content_En") != null)
                    {
                        content_En = ele.Attribute("Content_En").Value;
                    }
                    LangPackageCfg lpCfg = new LangPackageCfg()
                    {
                        LangKey = langKey,
                        Content = content,
                        Content_En = content_En,
                    };
                    docLangPackageDict.Add(lpCfg.LangKey, lpCfg);
                }
            }

            Dictionary<string, string> langPackageDict = clientLangPackageDict;
            if (side == ESideType.Server)
                langPackageDict = servertLangPackageDict;
            Dictionary<string, string>.Enumerator iter = langPackageDict.GetEnumerator();
            while (iter.MoveNext())
            {
                LangPackageCfg tempCfg;
                string langKey = iter.Current.Key;
                string langContent = iter.Current.Value;
                if (docLangPackageDict.ContainsKey(iter.Current.Key))
                {
                    tempCfg = docLangPackageDict[iter.Current.Key];
                }
                else
                {
                    tempCfg = new LangPackageCfg();
                    docLangPackageDict.Add(langKey, tempCfg);
                }
                tempCfg.LangKey = langKey;
                if(tempCfg.Content != langContent)   //若旧文本已修改，则清空其他语言文本
                {
                    tempCfg.Content_En = null; 
                }
                tempCfg.Content = langContent;
            }

            XDocument outputDoc = new XDocument();
            XElement outputRootEle = new XElement("Config");
            outputDoc.Add(outputRootEle);
            int idIndex = 1;
            foreach (LangPackageCfg tempCfg in docLangPackageDict.Values)
            {
                XElement ele = new XElement("LangPackageCfg");
                outputRootEle.Add(ele);
                ele.SetAttributeValue("ID", idIndex++);
                ele.SetAttributeValue("LangKey", tempCfg.LangKey);
                ele.SetAttributeValue("Content", tempCfg.Content);
                ele.SetAttributeValue("Content_En", tempCfg.Content_En);
            }

            outputDoc.Save(langPackagePath);
        }

        private class LangPackageCfg
        {
            /// <summary>
            /// 关键词
            /// </summary>
            public string LangKey { get; set; }
            /// <summary>
            /// 文本内容
            /// </summary>
            public string Content { get; set; }
            /// <summary>
            /// 文本内容（英文）
            /// </summary>
            public string Content_En { get; set; }
        }
        #endregion
    }
}
